/**
 * File: NodeImpl.java
 * Created by: mhaimel
 * Created on: Mar 5, 2010
 * CVS:  $Id: NodeImpl.java 1.0 Mar 5, 2010 1:15:44 PM mhaimel Exp $
 */
package uk.ac.ebi.curtain.model.graph.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.commons.lang.StringUtils;

import uk.ac.ebi.curtain.model.graph.Arc;
import uk.ac.ebi.curtain.model.graph.Node;
import uk.ac.ebi.curtain.model.graph.NodeEdge;
import uk.ac.ebi.curtain.model.graph.curtain.ContigInfo;
import uk.ac.ebi.curtain.model.graph.curtain.ReadWrapper;


/**
 * @author mhaimel
 *
 */
public class NodeImpl<N,A> implements Node<N, A> {

	private final Long id;
	private final CopyOnWriteArrayList<Arc<N,A>> startArcs;
	private final CopyOnWriteArrayList<Arc<N,A>> endArcs;
	private final AtomicReference<N> value = new AtomicReference<N>(null); // Object, which describes this node
	private final AtomicInteger status = new AtomicInteger(0);
	private final AtomicBoolean active = new AtomicBoolean(true);
	
	public NodeImpl(Long id) {
		this.id = id;
		this.startArcs = new CopyOnWriteArrayList<Arc<N,A>>();
		this.endArcs = new CopyOnWriteArrayList<Arc<N,A>>();
		this.status.set(0);
		this.active.set(true);
	}
	
	@Override
	public String toString() {
		return StringUtils.join(new Object[]{
			id,
			"leftCnt="+startArcs.size(),
			"rightCnt="+endArcs.size(),
			"status="+status,
			"active="+active,
			"hasValue="+(value!=null)
		},';');
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
//		result = prime * result + ((active == null) ? 0 : Boolean.valueOf(active.get()).hashCode());
//		result = prime * result + ((status == null) ? 0 : Integer.valueOf(status.get()).hashCode());
//		result = prime * result + ((startArcs == null) ? 0 : startArcs.hashCode());
//		result = prime * result + ((endArcs == null) ? 0 : endArcs.hashCode());
//		result = prime * result + ((value.get() == null) ? 0 : value.get().hashCode());
		return result;
	}

	/**
	 * {@inheritDoc}
	 */
	@SuppressWarnings("unchecked")
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		NodeImpl other = (NodeImpl) obj;
		if (id == null) {
			if (other.id != null)
				return false;
		} else if (!id.equals(other.id))
			return false;
		if (active == null) {
			if (other.active != null)
				return false;
		} else if (!active.get() == other.active.get())
			return false;
		if (status == null) {
			if (other.status != null)
				return false;
		} else if (status.get() != other.status.get())
			return false;
		if (startArcs == null) {
			if (other.startArcs != null)
				return false;
		} else if (!startArcs.equals(other.startArcs))
			return false;
		if (endArcs == null) {
			if (other.endArcs != null)
				return false;
		} else if (!endArcs.equals(other.endArcs))
			return false;
		if (value.get() == null) {
			if (other.value.get() != null)
				return false;
		} else if (!value.get().equals(other.value.get()))
			return false;
		return true;
	}
	
	@Override
	public Long getId() {
		return id;
	}

	@Override
	public N getValue() {
		return value.get();
	}
	
	@Override
	public void setValue(N value) {
		this.value.set(value);
	}

	/**
	 * @return the status
	 */
	@Override
	public AtomicInteger getStatus() {
		return status;
	}
	/**
	 * @return the active
	 */
	@Override
	public AtomicBoolean getActive() {
		return active;
	}	

	@Override
	public boolean isActive(){
		return getActive().get();
	}

	@Override
	public void setStatus(Integer status) {
		this.status.set(status);
	}

	@Override
	public void setActive(boolean active) {
		this.active.set(active);
	}

	@Override
	public List<Arc<N,A>> getRight(){
		return Collections.unmodifiableList(endArcs);
	}
	@Override
	public List<Arc<N,A>> getLeft(){
		return Collections.unmodifiableList(startArcs);
	}
	@Override
	public Arc<N, A> getArcRight(Node<N, A> right, NodeEdge left) {
		return getArc(endArcs,right,NodeEdge.End,left);
	}
	
	@Override
	public Arc<N, A> getArcLeft(Node<N, A> left, NodeEdge right) {
		return getArc(startArcs,left,NodeEdge.Start,right);
	}

	@Override
	public void registerArcRight(Arc<N, A> arc) {
		endArcs.add(arc);
	}
	@Override
	public void registerArcLeft(Arc<N, A> arc) {
		startArcs.add(arc);
	}

	@Override
	public void removeArc(Arc<N,A> arc){
		removeArc(arc, true);
	}
	@Override
	public void removeArc(Arc<N,A> arc, boolean failIfMissing){
		if(NodeEdge.End.equals(arc.onNode(this))){
			removeArc(arc,endArcs,failIfMissing);
		} else if(NodeEdge.Start.equals(arc.onNode(this))){
			removeArc(arc,startArcs,failIfMissing);
		}
	}
	
	@Override
	public List<Arc<N, A>> hasArc(Node<ContigInfo, ReadWrapper> nodeC) {
		List<Arc<N, A>> aList = new ArrayList<Arc<N,A>>();
		for(Arc<N, A> l : this.getLeft()){
			if(l.getOtherNode(this).equals(nodeC)){
				aList.add(l);
			}
		}
		for(Arc<N, A> r : this.getRight()){
			if(r.getOtherNode(this).equals(nodeC)){
				aList.add(r);
			}
		}
		return aList;
	}
	
	private void removeArc(Arc<N, A> arc,CopyOnWriteArrayList<Arc<N, A>> list, boolean failIfMissing) {
		if(!list.remove(arc)){
			if(failIfMissing)
				throw new IllegalArgumentException("Arc not in list: " + arc + "; " + list);
		}
	}	
	
	private Arc<N, A> getArc(CopyOnWriteArrayList<Arc<N, A>> arr, Node<N, A> node, NodeEdge right, NodeEdge left){
		if(NodeEdge.End.equals(right)){
			for(Arc<N, A> tmp : arr){
				if(tmp.getOtherNode(this).equals(node) && tmp.onOtherNode(this).equals(left)){
					return tmp;
				}
			}
		} else {
			for(Arc<N, A> tmp : arr){
				if(tmp.getOtherNode(this).equals(node) && tmp.onOtherNode(this).equals(left)){
					return tmp;
				}
			}			
		}
		return null;
	}
	
	public Collection<Arc<N,A>> getArcsTo(NodeEdge edge, NodeImpl<N, A> target){
		Set<Arc<N,A>> arcColl = new HashSet<Arc<N,A>>();
		for(Arc<N,A> a : getArcs(edge)){
			if(a.getOtherNode(this).equals(target)){
				arcColl.add(a);
			}
		}
		return arcColl;
	}

	@Override
	public List<Arc<N,A>> getArcs(NodeEdge edge) {
		if(NodeEdge.Start.equals(edge)){
			return getLeft();
		} else if(NodeEdge.End.equals(edge)){
			return getRight();
		} 
		throw new IllegalArgumentException("Edge not supported: " + edge);
	}
	
}
